﻿// Copyright © Microsoft Corporation. 
// This source is subject to the Microsoft Source License for Silverlight Controls (March 2008 Release).
// Please see http://go.microsoft.com/fwlink/?LinkID=111693 for details.
// All other rights reserved. 

using System.Diagnostics;
using System.Windows.Input; 
using System.Windows.Markup; 
using System.Windows.Media.Animation;
using System.Windows;
using System.Windows.Controls;
using System.Diagnostics.CodeAnalysis;
using System.Windows.Controls.Primitives;
using System;
using NASA.BeAMartian.Controls.Primitives;

namespace NASA.BeAMartian.Controls
{
    /// <summary> 
    /// Initializes a new instance of the Thumb class.
    /// </summary>
    [TemplatePart(Name = ResizableThumb.ElementTemplateBorderName, Type = typeof(FrameworkElement))]
    [TemplateVisualState(Name = VisualStates.StateNormal, GroupName = VisualStates.GroupCommon)]
    [TemplateVisualState(Name = VisualStates.StateMouseOver, GroupName = VisualStates.GroupCommon)]
    [TemplateVisualState(Name = VisualStates.StatePressed, GroupName = VisualStates.GroupCommon)]
    [TemplateVisualState(Name = VisualStates.StateDisabled, GroupName = VisualStates.GroupCommon)]
    [TemplateVisualState(Name = VisualStates.StateSelected, GroupName = VisualStates.GroupSelection)]
    [TemplateVisualState(Name = VisualStates.StateUnselected, GroupName = VisualStates.GroupSelection)]
    [TemplateVisualState(Name = VisualStates.StateSelectedInactive, GroupName = VisualStates.GroupSelection)]
    [TemplateVisualState(Name = VisualStates.StateUnfocused, GroupName = VisualStates.GroupFocus)]
    [TemplateVisualState(Name = VisualStates.StateFocused, GroupName = VisualStates.GroupFocus)]
    public class ResizableThumb : ContentControl
    {
        #region Template Parts

        /// <summary>
        /// Root of the thumb template. 
        /// </summary> 
        internal FrameworkElement ElementTemplateBorder { get; set; }
        internal const string ElementTemplateBorderName = "BorderElement";

        #endregion Template Parts

        #region Events

        /// <summary> 
        /// Identifies the DragStarted routed event. 
        /// </summary>
        public event EventHandler<DragStartedEventArgs> DragStarted;

        /// <summary>
        /// Identifies the DragDelta routed event. 
        /// </summary>
        public event EventHandler<DragDeltaEventArgs> DragDelta;

        /// <summary> 
        /// Occurs when the Thumb control loses mouse capture.
        /// </summary> 
        public event EventHandler<DragCompletedEventArgs> DragCompleted;

        /// <summary> 
        /// Identifies the ResizeStarted routed event. 
        /// </summary>
        public event EventHandler<ResizeStartedEventArgs> ResizeStarted;

        /// <summary>
        /// Identifies the ResizeDelta routed event.
        /// </summary>
        public event EventHandler<ResizeEventArgs> ResizeDelta;

        /// <summary> 
        /// Occurs when the ResizeCompleted control loses mouse capture.
        /// </summary> 
        public event EventHandler<ResizeCompletedEventArgs> ResizeCompleted;

        /// <summary> 
        /// Occurs when the IsSelected state changes to true.
        /// </summary> 
        public event EventHandler Selected;

        /// <summary> 
        /// Occurs when the IsSelected state changes to false.
        /// </summary> 
        public event EventHandler Unselected;

        #endregion Events

        #region Member Variables

        /// <summary>
        /// Whether the mouse is currently over the control
        /// </summary> 
        internal bool _isMouseOver;
        /// <summary>
        /// Origin of the thumb's drag operation. 
        /// </summary> 
        internal Point _origin;
        /// <summary> 
        /// Last position of the thumb while during a drag operation.
        /// </summary>
        internal Point _previousPosition;

        #endregion Member Variables

        #region Constructor
        /// <summary>
        /// Initializes a new instance of the Thumb class. 
        /// </summary> 
        public ResizableThumb()
        {
            IsEnabled = true;
            this.GotFocus += delegate { IsFocused = true; };
            this.LostFocus += delegate { IsFocused = false; };
            this.IsEnabledChanged += delegate { UpdateVisualState(); };

            DefaultStyleKey = typeof(ResizableThumb);
        }

        /// <summary> 
        /// Apply a template to the thumb.
        /// </summary>
        public override void OnApplyTemplate()
        {
            base.OnApplyTemplate();

            // Get the parts 
            if(this.ElementTemplateBorder != null)
            {
                this.ElementTemplateBorder.MouseLeftButtonDown -= new MouseButtonEventHandler(ElementTemplateBorder_MouseLeftButtonDown);
            }

            ElementTemplateBorder = GetTemplateChild(ElementTemplateBorderName) as FrameworkElement;

            if (this.ElementTemplateBorder != null)
            {
                this.ElementTemplateBorder.MouseLeftButtonDown += new MouseButtonEventHandler(ElementTemplateBorder_MouseLeftButtonDown);
            }
        }

        #endregion Constructor

        #region IsFocused
        /// <summary>
        /// Gets a value indicating whether this element has logical focus.
        /// </summary>
        public bool IsFocused
        {
            get { return (bool)GetValue(IsFocusedProperty); }
            internal set { SetValue(IsFocusedProperty, value); }
        }

        /// <summary>
        /// Identifies the
        /// <see cref="P:System.Windows.Controls.TabItem.IsFocused" />
        /// dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the
        /// <see cref="P:System.Windows.Controls.TabItem.IsFocused" />
        /// dependency property.
        /// </value>
        public static readonly DependencyProperty IsFocusedProperty =
            DependencyProperty.Register(
                "IsFocused",
                typeof(bool),
                typeof(ResizableThumb),
                new PropertyMetadata(OnIsFocusedPropertyChanged));

        /// <summary>
        /// IsFocusedProperty property changed handler.
        /// </summary>
        /// <param name="d">TabItem that changed IsFocused.</param>
        /// <param name="e">The DependencyPropertyChangedEventArgs.</param>
        private static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ResizableThumb ti = d as ResizableThumb;
            Debug.Assert(ti != null, "TabItem should not be null!");

            ti.OnIsFocusChanged(e);
        }

        /// <summary>
        /// Called when the IsFocused property changes.
        /// </summary>
        /// <param name="e">
        /// A <see cref="T:System.Windows.DependencyPropertyChangedEventArgs" />
        /// that contains the event data.
        /// </param>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
        protected virtual void OnIsFocusChanged(DependencyPropertyChangedEventArgs e)
        {
            UpdateVisualState();
        }
        #endregion IsFocused

        #region IsSelected
        /// <summary>
        /// Gets a value indicating whether this element is selected.
        /// </summary>
        public bool IsSelected
        {
            get { return (bool)GetValue(IsSelectedProperty); }
            set { SetValue(IsSelectedProperty, value); }
        }

        /// <summary>
        /// Identifies the
        /// <see cref="P:System.Windows.Controls.TabItem.IsSelected" />
        /// dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the
        /// <see cref="P:System.Windows.Controls.TabItem.IsSelected" />
        /// dependency property.
        /// </value>
        public static readonly DependencyProperty IsSelectedProperty =
            DependencyProperty.Register(
                "IsSelected",
                typeof(bool),
                typeof(ResizableThumb),
                new PropertyMetadata(OnIsSelectedPropertyChanged));

        /// <summary>
        /// IsSelectedProperty property changed handler.
        /// </summary>
        /// <param name="d">TabItem that changed IsSelected.</param>
        /// <param name="e">The DependencyPropertyChangedEventArgs.</param>
        private static void OnIsSelectedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ResizableThumb ti = d as ResizableThumb;
            Debug.Assert(ti != null, "TabItem should not be null!");

            ti.OnIsSelectedChanged(e);
        }

        /// <summary>
        /// Called when the IsSelected property changes.
        /// </summary>
        /// <param name="e">
        /// A <see cref="T:System.Windows.DependencyPropertyChangedEventArgs" />
        /// that contains the event data.
        /// </param>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
        protected virtual void OnIsSelectedChanged(DependencyPropertyChangedEventArgs e)
        {
            if (this.IsSelected)
            {
                if (this.Selected != null)
                    this.Selected(this, EventArgs.Empty);
            }
            else
            {
                if (this.Unselected != null)
                    this.Unselected(this, EventArgs.Empty);
            }

            UpdateVisualState();
        }
        #endregion IsSelected

        #region IsResizable
        /// <summary>
        /// Gets a value indicating whether this element is resizable.
        /// </summary>
        public bool IsResizable
        {
            get { return (bool)GetValue(IsResizableProperty); }
            internal set { SetValue(IsResizableProperty, value); }
        }

        /// <summary>
        /// Identifies the
        /// <see cref="P:System.Windows.Controls.TabItem.IsResizable" />
        /// dependency property.
        /// </summary>
        /// <value>
        /// The identifier for the
        /// <see cref="P:System.Windows.Controls.TabItem.IsResizable" />
        /// dependency property.
        /// </value>
        public static readonly DependencyProperty IsResizableProperty =
            DependencyProperty.Register(
                "IsResizable",
                typeof(bool),
                typeof(ResizableThumb),
                new PropertyMetadata(true, OnIsResizablePropertyChanged));

        /// <summary>
        /// IsResizableProperty property changed handler.
        /// </summary>
        /// <param name="d">TabItem that changed IsResizable.</param>
        /// <param name="e">The DependencyPropertyChangedEventArgs.</param>
        private static void OnIsResizablePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ResizableThumb ti = d as ResizableThumb;
            Debug.Assert(ti != null, "ResizableThumb should not be null!");

            ti.OnIsResizableChanged(e);
        }

        /// <summary>
        /// Called when the IsResizable property changes.
        /// </summary>
        /// <param name="e">
        /// A <see cref="T:System.Windows.DependencyPropertyChangedEventArgs" />
        /// that contains the event data.
        /// </param>
        [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
        protected virtual void OnIsResizableChanged(DependencyPropertyChangedEventArgs e)
        {
            UpdateVisualState();
        }
        #endregion IsResizable

        /// <summary>
        /// Called when the IsEnabled property changes.
        /// </summary>
        /// <param name="sender">
        /// Control that triggers this property change.
        /// </param>
        /// <param name="e">Property changed args.</param>
        private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e)
        {
            Debug.Assert(e.NewValue is bool, "New value should be a Boolean!");
            bool isEnabled = (bool)e.NewValue;
            if (!isEnabled)
            {
                this._isMouseOver = false;
            }

            UpdateVisualState();
        }

        #region Mode
        /// <summary>
        /// Gets whether the Thumb control has logical focus and mouse capture 
        /// and the left mouse button is pressed. 
        /// </summary>
        public ResizableThumbMode Mode
        {
            get { return (ResizableThumbMode)GetValue(ModeProperty); }
            internal set { SetValue(ModeProperty, value); }
        }

        /// <summary> 
        /// Identifies the Mode dependency property. 
        /// </summary>
        public static readonly DependencyProperty ModeProperty =
            DependencyProperty.Register(
                "Mode",
                typeof(ResizableThumbMode),
                typeof(ResizableThumb),
                new PropertyMetadata(OnModePropertyChanged));

        /// <summary> 
        /// ModeProperty property changed handler.
        /// </summary> 
        /// <param name="d">Thumb that changed Mode.</param>
        /// <param name="e">DependencyPropertyChangedEventArgs.</param>
        private static void OnModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            ResizableThumb thumb = d as ResizableThumb;
            Debug.Assert(thumb != null);

            thumb.OnModeChanged();
        }

        /// <summary>
        /// This method is invoked when the Mode property changes. 
        /// </summary>
        private void OnModeChanged()
        {
            UpdateVisualState();
        }

        #endregion Mode
 
        #region Mouse Handlers 

        /// <summary>
        /// Handle the MouseLeftButtonDown event for the border template.
        /// </summary> 
        /// <param name="e">MouseButtonEventArgs.</param> 
        internal void ElementTemplateBorder_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            if (this.IsResizable && this.Mode == ResizableThumbMode.None && IsEnabled)
            {
                this.Focus();

                CaptureMouse();

                this.IsSelected = true;
                e.Handled = true;
                this.Mode = ResizableThumbMode.Size;
                UpdateVisualState();

                Debug.Assert(this.Parent is UIElement);

                _previousPosition = e.GetPosition((UIElement)this.Parent);
                Point upperLeft = this.TransformToVisual((UIElement)this.Parent).Transform(new Point(0, 0));
                _origin = new Point(upperLeft.X + this.ActualWidth * this.RenderTransformOrigin.X, upperLeft.Y + this.ActualHeight * this.RenderTransformOrigin.Y);

                // Raise the DragStarted event
                bool success = false;
                try
                {
                    EventHandler<ResizeStartedEventArgs> handler = ResizeStarted;
                    if (handler != null)
                    {
                        handler(this, new ResizeStartedEventArgs(Math.Abs(_origin.X - _previousPosition.X), Math.Abs(_origin.Y - _previousPosition.Y)));
                    }
                    success = true;
                }
                finally
                {
                    // Cancel the drag if the DragStarted handler failed
                    if (!success)
                    {
                        CancelDrag();
                    }
                }
            }
        }

        /// <summary>
        /// Handle the MouseLeftButtonDown event.
        /// </summary> 
        /// <param name="e">MouseButtonEventArgs.</param> 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
        protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) 
        {
            if (this.Mode == ResizableThumbMode.None && IsEnabled)
            {
                this.Focus();

                CaptureMouse();

                this.IsSelected = true;
                e.Handled = true;
                this.Mode = ResizableThumbMode.Drag;
                UpdateVisualState();

                Debug.Assert(this.Parent is UIElement); 

                _origin = _previousPosition = e.GetPosition((UIElement)this.Parent);
 
                // Raise the DragStarted event
                bool success = false;
                try 
                { 
                    EventHandler<DragStartedEventArgs> handler = DragStarted;
                    if (handler != null) 
                    {
                        handler(this, new DragStartedEventArgs(_origin.X, _origin.Y));
                    } 
                    success = true;
                }
                finally 
                { 
                    // Cancel the drag if the DragStarted handler failed
                    if (!success) 
                    {
                        CancelDrag();
                    } 
                }
            }
        } 
 
        /// <summary>
        /// Handle the MouseLeftButtonUp event. 
        /// </summary>
        /// <param name="e">MouseButtonEventArgs.</param>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")] 
        protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e)
        {
            if (this.Mode != ResizableThumbMode.None && IsEnabled) 
            { 
                e.Handled = true;
                this.Mode = ResizableThumbMode.None;
                ReleaseMouseCapture();
                RaiseDragCompleted(false);
            }

            UpdateVisualState();
        }

        /// <summary> 
        /// Handle the MouseEnter event. 
        /// </summary>
        /// <param name="e">MouseEventArgs.</param> 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
        protected override void OnMouseEnter(MouseEventArgs e)
        { 
            if (IsEnabled)
            { 
                _isMouseOver = true; 
                UpdateVisualState();
            } 
        }

        /// <summary> 
        /// Handle the MouseLeave event.
        /// </summary>
        /// <param name="e">MouseEventArgs.</param> 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")] 
        protected override void OnMouseLeave(MouseEventArgs e)
        { 
            if (IsEnabled)
            { 
                _isMouseOver = false;
                UpdateVisualState();
            } 
        } 

        /// <summary> 
        /// Handle the MouseMove event.
        /// </summary>
        /// <param name="e">MouseEventArgs.</param> 
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "e", Justification = "Compat with WPF.")]
        protected override void OnMouseMove(MouseEventArgs e)
        { 
            if (this.Mode != ResizableThumbMode.None) 
            {
                Debug.Assert(this.Parent is UIElement); 

                Point position = e.GetPosition((UIElement)this.Parent);

                if (this.Mode == ResizableThumbMode.Drag)
                {
                    if (position != _previousPosition)
                    {
                        // Raise the DragDelta event
                        EventHandler<DragDeltaEventArgs> handler = DragDelta;
                        if (handler != null)
                        {
                            handler(this, new DragDeltaEventArgs(position.X - _previousPosition.X, position.Y - _previousPosition.Y));
                        }

                        _previousPosition = position;
                    }
                }
                else
                {
                    if (position != _origin)
                    {
                        // Raise the DragDelta event
                        EventHandler<ResizeEventArgs> handler = ResizeDelta;
                        if (handler != null)
                        {
                            handler(this, new ResizeEventArgs(Math.Abs(_origin.X - position.X), Math.Abs(_origin.Y - position.Y)));
                        }
                    }
                }
            }
        } 
        #endregion Mouse Handlers

        #region Change State 

        /// <summary>
        /// Change to the correct visual state for the TabItem.
        /// </summary>
        internal void UpdateVisualState()
        {
            ChangeVisualState(true);
        }

        /// <summary>
        /// Change to the correct visual state for the TabItem.
        /// </summary>
        /// <param name="useTransitions">
        /// True to use transitions when updating the visual state, false to
        /// snap directly to the new visual state.
        /// </param>
        private void ChangeVisualState(bool useTransitions)
        {
            // Handle the Common states
            if (!this.IsEnabled)
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StateDisabled, VisualStates.StateNormal);
            }
            else if (this.Mode != ResizableThumbMode.None)
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StatePressed, VisualStates.StateMouseOver, VisualStates.StateNormal);
            }
            else if (this._isMouseOver)
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StateMouseOver, VisualStates.StateNormal);
            }
            else
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StateNormal);
            }

            // Handle the Focused states
            if (IsFocused)
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StateFocused, VisualStates.StateUnfocused);
            }
            else
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StateUnfocused);
            }

            if (IsSelected)
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StateSelected, VisualStates.StateUnselected);
            }
            else
            {
                VisualStates.GoToState(this, useTransitions, VisualStates.StateUnselected);
            }
        }

        #endregion Change State

        #region Drag Cancel/Complete 
        /// <summary> 
        /// Cancel a drag operation if it is currently in progress.
        /// </summary> 
        public void CancelDrag()
        {
            if (this.Mode == ResizableThumbMode.Drag) 
            {
                this.Mode = ResizableThumbMode.None;
                UpdateVisualState();
                RaiseDragCompleted(true); 
            } 
        }
 
        /// <summary>
        /// Raise the DragCompleted event.
        /// </summary> 
        /// <param name="canceled">
        /// A Boolean value that indicates whether the drag operation was
        /// canceled by a call to the CancelDrag method. 
        /// </param> 
        private void RaiseDragCompleted(bool canceled)
        { 
            EventHandler<DragCompletedEventArgs> handler = DragCompleted;
            if (handler != null)
            { 
                DragCompletedEventArgs args = new DragCompletedEventArgs(
                    _previousPosition.X - _origin.X,
                    _previousPosition.Y - _origin.Y, 
                    canceled); 
                handler(this, args);
            } 
        }
        #endregion Drag Cancel/Complete

        #region Resize Cancel/Complete
        /// <summary> 
        /// Cancel a drag operation if it is currently in progress.
        /// </summary> 
        public void CancelResize()
        {
            if (this.Mode == ResizableThumbMode.Size)
            {
                this.Mode = ResizableThumbMode.None;
                UpdateVisualState();
                RaiseResizeCompleted(true);
            } 
        }

        /// <summary>
        /// Raise the DragCompleted event.
        /// </summary> 
        /// <param name="canceled">
        /// A Boolean value that indicates whether the drag operation was
        /// canceled by a call to the CancelDrag method. 
        /// </param> 
        private void RaiseResizeCompleted(bool canceled)
        {
            EventHandler<ResizeCompletedEventArgs> handler = ResizeCompleted;
            if (handler != null)
            {
                double previousDistX = Math.Abs(_origin.X - _previousPosition.X);
                double previousDistY = Math.Abs(_origin.Y - _previousPosition.Y);
                handler(this, new ResizeCompletedEventArgs(previousDistX, previousDistY, canceled));
            }
        }
        #endregion Drag Cancel/Complete
    } 
}
